<?php

declare(strict_types=1);

namespace Reezy\APIToken;


use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\SimpleCache\CacheInterface;
use function config;
use function is_null;
use function substr;

class APITokenManager
{
    private $map = [];
    /**
     * @var CacheInterface
     */
    private $cache;

    private $config;
    private $headerTokenKey;
    private $paramsTokenKey;
    /**
     * @var EventDispatcherInterface
     */
    private $event;

    public function __construct(CacheInterface $cache, EventDispatcherInterface $event)
    {
        $this->config = config("api_token", []);
        $this->headerTokenKey = $this->config['header_token_key'] ?? 'x-token';
        $this->paramsTokenKey = $this->config['params_token_key'] ?? 'token';

        $this->cache = $cache;
        $this->event = $event;
    }

    public function getHeaderTokenKey(): string
    {
        return $this->headerTokenKey;
    }

    public function get(string $name): APIToken
    {
        if (isset($this->map[$name])) {
            return $this->map[$name];
        }
        return $this->map[$name] = new APIToken($name, $this->config[$name] ?? [], $this->cache);
    }

    public function check(ServerRequestInterface $request): array
    {
        $token = $this->getToken($request);

        $checker = $this->get($token->name);

        if ($checker == null) {
            throw new APITokenException('no match token handler');
        }
        [$newToken, $now] = $checker->check($request, $token);

        $newRequest = $request->withAttribute('userId', $token->userId)->withAttribute(APIToken::class, $this);

        $this->event->dispatch(new APITokenChecked($token->name, $token->userId, $now));

        return [$newRequest, $newToken];
    }

    private function getToken(ServerRequestInterface $request): APITokenInfo
    {
        $header = $request->getHeaderLine('authorization');
        if (!empty($header) && substr($header, 0, 7) === 'Bearer ') {
            $token = substr($header, 7);
        }
        if (empty($token)) {
            $token = $request->getHeaderLine($this->headerTokenKey)
                ?? $request->getQueryParams()[$this->paramsTokenKey]
                ?? $request->getParsedBody()[$this->paramsTokenKey]
                ?? $request->getCookieParams()[$this->headerTokenKey]
                ?? '';
        }
        if (empty($token)) {
            throw new APITokenException("token not found");
        }
        $decoded = APITokenUtil::decode($token);
        if (is_null($decoded)) {
            throw new APITokenException("token format error");
        }
        return $token;
    }
}
